From: Joseph Marrero Corchado Date: Sat, 9 May 2026 19:18:07 +0000 (-0400) Subject: sysroot: Merge bootconfig-extra from previously staged deployment X-Git-Tag: archive/raspbian/2026.2-1+rpi1^2~9^2^2~5^2 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=4c0d3a819033166ec9753ff8c5fdac5db1bac813;p=ostree.git sysroot: Merge bootconfig-extra from previously staged deployment When a staged deployment is replaced by a new one (e.g. bootc sets source-tracked kargs, then rpm-ostree appends a karg before reboot), the bootconfig-extra keys from the first staging were lost. The new staging only checked the new deployment's bootconfig (always empty) and fell back to the merge deployment's bootconfig (the booted BLS file), which had no knowledge of what was in the previous staged GVariant. Fix this by merging bootconfig-extra from three sources in priority order: 1. Merge deployment's bootconfig (lowest priority) 2. Previously staged deployment's bootconfig-extra 3. New deployment's bootconfig (highest priority) This ensures that x-options-source-* keys set by one consumer survive re-staging by another consumer on the same boot. Assisted-by: OpenCode (Claude Opus 4.6) Signed-off-by: Joseph Marrero Corchado --- diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 77b601ff..add835fb 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -3795,14 +3795,25 @@ _ostree_sysroot_ensure_finalize_staged_service (GError **error) return TRUE; } +/* Merge all entries from an a{ss} GVariant into a string→string hash table. + * Used to accumulate bootconfig-extra keys from multiple sources during staging. */ +static void +merge_extra_variant_into_table (GVariant *extra, GHashTable *table) +{ + GVariantIter iter; + const char *k, *v; + g_variant_iter_init (&iter, extra); + while (g_variant_iter_next (&iter, "{&s&s}", &k, &v)) + g_hash_table_insert (table, g_strdup (k), g_strdup (v)); +} + /** * ostree_sysroot_stage_tree_with_options: * @self: Sysroot - * @osname: (allow-none): osname to use for merge deployment + * @osname: osname to use for merge deployment * @revision: Checksum to add * @origin: (allow-none): Origin to use for upgrades - * @merge_deployment: (allow-none): Use this deployment for merge path - * @opts: Options + * @opts: (nullable): Options * @out_new_deployment: (out): The new deployment path * @cancellable: Cancellable * @error: Error @@ -3888,27 +3899,72 @@ ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, const char *osname, * These are custom keys set by consumers like bootc and need to survive * the staging roundtrip so they are preserved during finalization at shutdown. * - * First check the new deployment's bootconfig (in case the caller set keys - * on it directly). If none found, fall back to the merge deployment's - * bootconfig, which carries the keys from the currently deployed BLS entry. - * This ensures that x-prefixed keys are inherited across staged deployments - * even though _ostree_deployment_set_bootconfig_from_kargs() creates a fresh - * bootconfig containing only the "options" key. + * Extension keys can come from three sources, merged in increasing + * priority order (higher-priority sources override lower ones for + * the same key): + * + * 1. The merge deployment's bootconfig (on-disk BLS from the + * currently booted or pending deployment) — lowest priority + * 2. The previously staged deployment's bootconfig-extra (a prior + * consumer like bootc may have staged keys that would be lost + * when this new staging replaces the old staged GVariant) + * 3. The new deployment's bootconfig (caller set keys directly) + * — highest priority + * + * This merge ensures that e.g. `bootc loader-entries set-options-for-source` + * followed by `rpm-ostree kargs --append` on the same boot preserves the + * source keys that bootc wrote into the first staged deployment. */ { - GVariant *extra = NULL; - OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); - if (bootconfig) - extra = _ostree_bootconfig_parser_get_extra_keys_variant (bootconfig); - if (!extra && merge_deployment) + g_autoptr (GHashTable) merged_extra + = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + /* Priority 1 (lowest): merge deployment's bootconfig */ + if (merge_deployment) { OstreeBootconfigParser *merge_bootconfig = ostree_deployment_get_bootconfig (merge_deployment); if (merge_bootconfig) - extra = _ostree_bootconfig_parser_get_extra_keys_variant (merge_bootconfig); + { + g_autoptr (GVariant) merge_extra + = _ostree_bootconfig_parser_get_extra_keys_variant (merge_bootconfig); + if (merge_extra) + merge_extra_variant_into_table (merge_extra, merged_extra); + } + } + + /* Priority 2: previously staged deployment's bootconfig-extra. + * The staged deployment data is already loaded and cached in the + * OstreeSysroot during ostree_sysroot_load(). */ + if (self->staged_deployment_data) + { + g_autoptr (GVariant) prev_extra = g_variant_lookup_value ( + self->staged_deployment_data, "bootconfig-extra", (GVariantType *)"a{ss}"); + if (prev_extra) + merge_extra_variant_into_table (prev_extra, merged_extra); + } + + /* Priority 3 (highest): new deployment's bootconfig */ + { + OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); + if (bootconfig) + { + g_autoptr (GVariant) new_extra + = _ostree_bootconfig_parser_get_extra_keys_variant (bootconfig); + if (new_extra) + merge_extra_variant_into_table (new_extra, merged_extra); + } + } + + if (g_hash_table_size (merged_extra) > 0) + { + g_auto (GVariantBuilder) extra_builder = OT_VARIANT_BUILDER_INITIALIZER; + g_variant_builder_init (&extra_builder, (GVariantType *)"a{ss}"); + GLNX_HASH_TABLE_FOREACH_KV (merged_extra, const char *, k, const char *, v) + g_variant_builder_add (&extra_builder, "{ss}", k, v); + g_variant_builder_add (builder, "{sv}", "bootconfig-extra", + g_variant_builder_end (&extra_builder)); } - if (extra) - g_variant_builder_add (builder, "{sv}", "bootconfig-extra", extra); } const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED));